/**
 * Project Name:cloudwalk-common
 * File Name:HttpClientUtil.java
 * Package Name:cn.cloudwalk.common.net
 * Date:2016年3月25日下午2:21:18
 * Copyright @ 2010-2016 Cloudwalk Information Technology Co.Ltd  All Rights Reserved.
 *
*/

package cn.alise.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;



/**
 * ClassName:HttpClientUtil <br/>
 * Description: 基于HttpClient的工具类. <br/>
 * Date: 2016年3月25日 下午2:21:18 <br/>
 *
 * @author 陈腾
 * @version 1.0.0
 * @since JDK 1.7
 */
@SuppressWarnings("deprecation")
public class HttpClientUtil {

  private static final Logger log = LoggerFactory.getLogger(HttpClientUtil.class);

  /**
   * cm:Http客户端连接池
   */
  private static PoolingHttpClientConnectionManager cm;

  private static final String DEFAULT_CHARSET = "UTF-8";
  
  //http请求url分隔符
  public static final String SEPARATOR = "/";

  /**
   * retryCount:最多链接几次断开.
   */
  private static final int retryCount = 5;

  //请求重试处理
  static HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {

    @Override
    public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
      boolean result = false;

      if (exception == null) {
        throw new IllegalArgumentException("Exception parameter may not be null");
      } else if (context == null) {
        throw new IllegalArgumentException("HTTP context may not be null");
        // 如果已经重试了retryCount次，就放弃
      } else if (executionCount >= retryCount) {
        log.info("请求已经尝试了" + retryCount + "次，已经放弃请求！");
        return false;
      }
      // 如果服务器丢掉了连接，那么就重试
      if (exception instanceof NoHttpResponseException) {
        log.info("如果服务器丢掉了连接，正在尝试……");
        return true;
      }
      // 不要重试SSL握手异常
      if (exception instanceof SSLHandshakeException) {
        log.info("SSL握手异常……");
        return false;
      }
      // 超时
      if (exception instanceof InterruptedIOException) {
        log.info("请求超时……");
        return false;
      }
      // 目标服务器不可达
      if (exception instanceof UnknownHostException) {
        log.info("无法到达目标服务器……");
        return false;
      }
      // 连接被拒绝
      if (exception instanceof ConnectTimeoutException) {
        log.info("目标服务器拒绝……");
        return false;
      }
      // SSL握手异常
      if (exception instanceof SSLException) {
        log.info("SSL目标服务器拒绝……");
        return false;
      } else {
        HttpClientContext clientContext = HttpClientContext.adapt(context);
        HttpRequest request = clientContext.getRequest();
        // 如果请求是幂等的，就再次尝试
        if (!(request instanceof HttpEntityEnclosingRequest)) {
          log.info("正在尝试再次连接目标服务器……");
          return true;
        }
      }
      if (result) {
        log.info("请求失败,第" + executionCount + "次重试");
      }
      return result;
    }
  };

  private static void config(HttpRequestBase httpRequestBase) {
    ////设置请求报文头里的User-Agent字段
    httpRequestBase.setHeader("User-Agent",
        "Mozilla/5.0 (Windows NT 5.1; rv:6.0.2) Gecko/20100101 Firefox/6.0.2");
    httpRequestBase.setHeader("Accept",
        "text/html,application/xhtml+xml,application/xml,application/json,application/x-www-form-urlencoded;q=0.9,*/*;q=0.8");
    httpRequestBase.setHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");//"en-US,en;q=0.5");
    httpRequestBase.setHeader("Accept-Charset", "utf-8,gbk,gb2312,ISO-8859-1,;q=0.7,*;q=0.7");

    // 配置请求的超时设置
    RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(3000)
        .setConnectTimeout(3000).setSocketTimeout(60000).build();
    httpRequestBase.setConfig(requestConfig);
  }

  /**
   * covertParams2NVPS:将参数转换为NameValuePair对象，用于HTPP请求调用. <br/>
   *
   * @param params 参数列表
   * @return 返回 NameValuePair数组，用于存储欲传送的参数
   * @since JDK 1.7
   */
  private static ArrayList<NameValuePair> covertParams2NVPS(Map<String, Object> params) {
    ArrayList<NameValuePair> pairs = new ArrayList<NameValuePair>();
    for (Map.Entry<String, Object> param : params.entrySet()) {
      pairs.add(new BasicNameValuePair(param.getKey(), (String) param.getValue()));
    }

    return pairs;
  }
  
  /**
   * 通过连接池获取HttpClient
   *
   * @return CloseableHttpClient
   */
  private static CloseableHttpClient getHttpClient() {
    init();
    return HttpClients.custom().setConnectionManager(cm).setRetryHandler(httpRequestRetryHandler)
        .build();
  }

  /**
   * getResult:处理Http请求. <br/>
   *
   * @param request HttpRequestBase
   * @return String
   * @since JDK 1.7
   */
  private static HttpClientResult getResult(HttpRequestBase request, final String resultCharType)
      throws Exception {
    //CloseableHttpClient httpClient = HttpClients.createDefault();
    CloseableHttpClient httpClient = getHttpClient();
    try {
      CloseableHttpResponse response = httpClient.execute(request);
      int code = response.getStatusLine().getStatusCode();
      HttpEntity entity = response.getEntity();
      String result = "";
      if (entity != null) {
        //long len = entity.getContentLength();// -1 表示长度未知
        if (!StringUtils.isEmpty(resultCharType)) { //如果不是空字符串就返回指定的字符
          result = EntityUtils.toString(entity, resultCharType);
        } else {
          result = EntityUtils.toString(entity, DEFAULT_CHARSET);
        }
        response.close();
        //httpClient.close();
        HttpClientResult httpClientResult = new HttpClientResult();
        httpClientResult.setCode(code);
        httpClientResult.setResult(result);
        return httpClientResult;
      }
    } catch (ClientProtocolException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {

    }

    return null;
  }

  /**
   * httpGetRequest:HTTP的GET请求带有参数，Headers的HTTP请求.. <br/>
   *
   * @param url 请求的URL
   * @param headers headers参数
   * @param params 请求的参数
   * @param resultCharType
   * @return String 请求的结果
   * @throws URISyntaxException
   * @since JDK 1.7
   */
  public static HttpClientResult httpGetRequest(final String url, final Map<String, Object> headers,
      final Map<String, Object> params, final String resultCharType) throws Exception {
    URIBuilder ub = new URIBuilder();
    ub.setPath(url);

    ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
    ub.setParameters(pairs);

    HttpGet httpGet = new HttpGet(ub.build());
    for (Map.Entry<String, Object> param : headers.entrySet()) {
      httpGet.addHeader(param.getKey(), (String) param.getValue());
    }
    return getResult(httpGet, resultCharType);
  }

  /**
   * httpGetRequest:HTTP的GET请求带有参数的HTTP请求. <br/>
   *
   * @param url 请求的URL
   * @param params 请求的参数
   * @param resultCharType
   * @return 请求的结果
   * @throws URISyntaxException
   * @since JDK 1.7
   */
  public static HttpClientResult httpGetRequest(final String url, final Map<String, Object> params,
      final String resultCharType) throws Exception {
    URIBuilder ub = new URIBuilder();
    ub.setPath(url);

    ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
    ub.setParameters(pairs);

    HttpGet httpGet = new HttpGet(ub.build());
    return getResult(httpGet, resultCharType);
  }

  /**
   * httpGetRequest:httpGet 请求. <br/>
   *
   * @param url HTTP的GET请求的URL
   * @param resultCharType
   * @return 返回请求的结果
   * @since JDK 1.7
   */
  public static HttpClientResult httpGetRequest(final String url, final String resultCharType)
      throws Exception {

    HttpGet httpGet = new HttpGet(url);
    //做一些基本的配置
    config(httpGet);
    return getResult(httpGet, resultCharType);
  }
  /**
   * httpGetRequest:httpGet 请求. <br/>
   *
   * @param url HTTP的GET请求的URL
   * @param resultCharType
   * @return 返回请求的结果
   * @since JDK 1.7
   */
  public static HttpClientResult httpGetRequest(final String url, final String resultCharType,int timeOut)
      throws Exception {

    HttpGet httpGet = new HttpGet(url);
    //做一些基本的配置
    httpGet.setHeader("User-Agent",
            "Mozilla/5.0 (Windows NT 5.1; rv:6.0.2) Gecko/20100101 Firefox/6.0.2");
    httpGet.setHeader("Accept",
            "text/html,application/xhtml+xml,application/xml,application/json,application/x-www-form-urlencoded;q=0.9,*/*;q=0.8");
    httpGet.setHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");//"en-US,en;q=0.5");
    httpGet.setHeader("Accept-Charset", "utf-8,gbk,gb2312,ISO-8859-1,;q=0.7,*;q=0.7");

    // 配置请求的超时设置
    RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(timeOut)
        .setConnectTimeout(timeOut).setSocketTimeout(timeOut).build();
    httpGet.setConfig(requestConfig);
    return getResult(httpGet, resultCharType);
  }
  /**
   * httpPostRequest:带有Header和参数的HTTP的POST请求. <br/>
   *
   * @param url 请求的URL
   * @param headers 请求的Header
   * @param params 请求的参数
   * @param resultCharType
   * @return String 返回请求结果
   * @throws UnsupportedEncodingException
   * @since JDK 1.7
   */
  public static HttpClientResult httpPostRequest(final String url, Map<String, Object> headers,
      final Map<String, Object> params, final String resultCharType) throws Exception {
    HttpPost httpPost = new HttpPost(url);

    for (Map.Entry<String, Object> param : headers.entrySet()) {
      httpPost.addHeader(param.getKey(), (String) param.getValue());
    }

    ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
    httpPost.setEntity(new UrlEncodedFormEntity(pairs, DEFAULT_CHARSET));
    return getResult(httpPost, resultCharType);
  }

  /**
   * httpPostRequest:带有参数的HTTP的POST请求. <br/>
   *
   * @param url 请求的URL
   * @param params 请求参数
   * @param resultCharType
   * @return String 返回请求结果
   * @throws UnsupportedEncodingException
   * @since JDK 1.7
   */
  public static HttpClientResult httpPostRequest(final String url, final Map<String, Object> params,
      final String resultCharType) throws Exception {
    HttpPost httpPost = new HttpPost(url);
    ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
    httpPost.setEntity(new UrlEncodedFormEntity(pairs, DEFAULT_CHARSET));
    return getResult(httpPost, resultCharType);
  }
  
  /**
   * httpPostRequest:无参数HTTP的POST请求. <br/>
   *
   * @param url POST请求的URL
   * @param resultCharType
   * @return String 返回请求结果
   * @since JDK 1.7
   */
  public static HttpClientResult httpPostRequest(final String url, final String resultCharType)
      throws Exception {
    final HttpPost httpPost = new HttpPost(url);
    return getResult(httpPost, resultCharType);
  }

  /**
   * httpPostWithJSONRequest:带有Header和JSON参数的HTTP的POST请求. <br/>
   *
   * @param url 请求的URL
   * @param headers 请求的Header
   * @param String 请求的参数JSON串
   * @param resultCharType
   * @return String 返回请求结果
   * @throws UnsupportedEncodingException
   * @since JDK 1.7
   */
  public static HttpClientResult httpPostWithJSONRequest(final String url, final Map<String, Object> headers,
      final String json, final String resultCharType) throws Exception {
    HttpPost httpPost = new HttpPost(url);
    //将JSON转换为StringEntity
    StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);
    for (Map.Entry<String, Object> param : headers.entrySet()) {
      httpPost.addHeader(param.getKey(), (String) param.getValue());
    }
    httpPost.setEntity(stringEntity);
    return getResult(httpPost, resultCharType);
  }

  /**
   * httpPostWithJSONRequest:带有Header和JSON参数的HTTP的POST请求. <br/>
   *
   * @param url 请求的URL
   * @param String 请求的参数JSON串
   * @param resultCharType
   * @return String 返回请求结果
   * @throws UnsupportedEncodingException
   * @since JDK 1.7
   */
  public static HttpClientResult httpPostWithJSONRequest(final String url, final String json,
      final String resultCharType) throws Exception {
    HttpPost httpPost = new HttpPost(url);
    //将JSON转换为StringEntity
    StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);
    httpPost.setEntity(stringEntity);
    RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(3000)
            .setConnectTimeout(3000).setSocketTimeout(60000).build();
    httpPost.setConfig(requestConfig);
    return getResult(httpPost, resultCharType);
  }
  
  /** 
   * 无需本地证书keyStore的SSL https带参数请求 
   *  
   * @param url 
   * @param paramsMap 
   * @param encoding 
   * @return 
   */  
  public static String postSSLUrlWithParams(String url, Map<String, String> reqMap, String encoding) {  
      log.info("httpsClient访问开始...");  
      CloseableHttpClient httpClient = HttpClientUtil.createSSLInsecureClient();  
      HttpPost post = new HttpPost(url);  
      // 添加参数  
      List<NameValuePair> params = new ArrayList<>();  
      if (reqMap != null && reqMap.keySet().size() > 0) {  
          Iterator<Map.Entry<String, String>> iter = reqMap.entrySet().iterator();  
          while (iter.hasNext()) {  
              Map.Entry<String, String> entity = iter.next();  
              params.add(new BasicNameValuePair(entity.getKey(), entity.getValue()));  
          }  
      }  
      StringBuilder sb = new StringBuilder();  
      BufferedReader br = null;  
      try {  
          // 设置客户端请求的头参数getParams已经过时,现在用requestConfig对象替换  
          // httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,CONNECTION_TIMEOUT);  
          RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(5000)  
                  .setSocketTimeout(5000).build();  
          post.setConfig(requestConfig);  
          // 设置编码格式  
          post.setEntity(new UrlEncodedFormEntity(params, encoding));  
          HttpResponse response = httpClient.execute(post);  
          HttpEntity httpEntity = response.getEntity();  
          br = new BufferedReader(new InputStreamReader(httpEntity.getContent(),encoding));  
          String s = null;  
          while((s=br.readLine())!=null){  
              sb.append(s);  
          }  
      } catch (UnsupportedEncodingException e) {  
          log.error("编码格式输入错误", e);  
          throw new RuntimeException("指定的编码集不对,您目前指定的编码集是:" + encoding);  
      } catch (ClientProtocolException e) {  
          e.printStackTrace();  
      } catch (IOException e) {  
          log.error("读取流文件异常", e);  
          throw new RuntimeException("读取流文件异常",e);  
      }catch (Exception e) {  
          log.error("通讯未知系统异常", e);  
          throw new RuntimeException("通讯未知系统异常",e);  
      }finally{  
          if(br != null){  
              try {  
                  br.close();  
              } catch (IOException e) {  
                  log.error("关闭br异常" + e);  
                  e.printStackTrace();  
              }  
          }  
      }  
      return sb.toString();  
  }
  
  /** 
   * 创建一个SSL信任所有证书的httpClient对象 
   *  
   * @return 
   */  
  public static CloseableHttpClient createSSLInsecureClient() {  
      try {  
          SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {  
              // 默认信任所有证书  
              public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {  
                  return true;  
              }  
          }).build();  
          // AllowAllHostnameVerifier: 这种方式不对主机名进行验证，验证功能被关闭，是个空操作(域名验证)  
          SSLConnectionSocketFactory sslcsf = new SSLConnectionSocketFactory(sslContext,  
                  SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);  
          return HttpClients.custom().setSSLSocketFactory(sslcsf).build();  
      } catch (KeyManagementException e) {  
          e.printStackTrace();  
      } catch (NoSuchAlgorithmException e) {  
          e.printStackTrace();  
      } catch (KeyStoreException e) {  
          e.printStackTrace();  
      }  
      return HttpClients.createDefault();  
  }

  private static void init() {
    ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
    LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
    Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create()
        .register("http", plainsf).register("https", sslsf).build();
    if (cm == null) {
      cm = new PoolingHttpClientConnectionManager(registry);

      //整个连接池最大连接数
      cm.setMaxTotal(200);
      // 将每个路由基础的连接增加到20
      cm.setDefaultMaxPerRoute(20);

    }
  }
}
